diff --git a/code/lib/client-api/src/ClientApi.ts b/code/lib/client-api/src/ClientApi.ts index b36e5589a363..b09c69a5ec6f 100644 --- a/code/lib/client-api/src/ClientApi.ts +++ b/code/lib/client-api/src/ClientApi.ts @@ -31,7 +31,7 @@ import { StoryStoreFacade } from './StoryStoreFacade'; // ClientApi (and StoreStore) are really singletons. However they are not created until the // relevant framework instanciates them via `start.js`. The good news is this happens right away. -let singleton: ClientApi; +let singleton: ClientApi; const warningAlternatives = { addDecorator: `Instead, use \`export const decorators = [];\` in your \`preview.js\`.`, @@ -111,10 +111,10 @@ export const setGlobalRender = (render: StoryFn) => { }; const invalidStoryTypes = new Set(['string', 'number', 'boolean', 'symbol']); -export class ClientApi { - facade: StoryStoreFacade; +export class ClientApi { + facade: StoryStoreFacade; - storyStore?: StoryStore; + storyStore?: StoryStore; private addons: Addon_ClientApiAddons; @@ -124,7 +124,7 @@ export class ClientApi } = {}) { + constructor({ storyStore }: { storyStore?: StoryStore } = {}) { this.facade = new StoryStoreFacade(); this.addons = {}; diff --git a/code/lib/client-api/src/StoryStoreFacade.ts b/code/lib/client-api/src/StoryStoreFacade.ts index 55c505890b0e..837585e990e1 100644 --- a/code/lib/client-api/src/StoryStoreFacade.ts +++ b/code/lib/client-api/src/StoryStoreFacade.ts @@ -21,8 +21,8 @@ import type { StoryStore } from '@storybook/store'; import { userOrAutoTitle, sortStoriesV6 } from '@storybook/store'; import { logger } from '@storybook/client-logger'; -export class StoryStoreFacade { - projectAnnotations: Store_NormalizedProjectAnnotations; +export class StoryStoreFacade { + projectAnnotations: Store_NormalizedProjectAnnotations; entries: Record; @@ -54,7 +54,7 @@ export class StoryStoreFacade) { + getStoryIndex(store: StoryStore) { const fileNameOrder = Object.keys(this.csfExports); const storySortParameter = this.projectAnnotations.parameters?.options?.storySort; diff --git a/code/lib/preview-web/src/Preview.tsx b/code/lib/preview-web/src/Preview.tsx index 81e1cfd14674..6cfe45605db4 100644 --- a/code/lib/preview-web/src/Preview.tsx +++ b/code/lib/preview-web/src/Preview.tsx @@ -44,18 +44,18 @@ export type MaybePromise = Promise | T; const renderToDOMDeprecated = deprecate(() => {}, `\`renderToDOM\` is deprecated, please rename to \`renderToRoot\``); -export class Preview { +export class Preview { serverChannel?: Channel; - storyStore: StoryStore; + storyStore: StoryStore; getStoryIndex?: () => Store_StoryIndex; importFn?: Store_ModuleImportFn; - renderToRoot?: RenderToRoot; + renderToRoot?: RenderToRoot; - storyRenders: StoryRender[] = []; + storyRenders: StoryRender[] = []; previewEntryError?: Error; @@ -83,7 +83,7 @@ export class Preview // getProjectAnnotations has been run, thus this slightly awkward approach getStoryIndex?: () => Store_StoryIndex; importFn: Store_ModuleImportFn; - getProjectAnnotations: () => MaybePromise>; + getProjectAnnotations: () => MaybePromise>; }) { // We save these two on initialization in case `getProjectAnnotations` errors, // in which case we may need them later when we recover. @@ -108,8 +108,8 @@ export class Preview } getProjectAnnotationsOrRenderError( - getProjectAnnotations: () => MaybePromise> - ): Store_PromiseLike> { + getProjectAnnotations: () => MaybePromise> + ): Store_PromiseLike> { return SynchronousPromise.resolve() .then(getProjectAnnotations) .then((projectAnnotations) => { @@ -136,9 +136,7 @@ export class Preview } // If initialization gets as far as project annotations, this function runs. - initializeWithProjectAnnotations( - projectAnnotations: ProjectAnnotations - ) { + initializeWithProjectAnnotations(projectAnnotations: ProjectAnnotations) { this.storyStore.setProjectAnnotations(projectAnnotations); this.setInitialGlobals(); @@ -199,7 +197,7 @@ export class Preview async onGetProjectAnnotationsChanged({ getProjectAnnotations, }: { - getProjectAnnotations: () => MaybePromise>; + getProjectAnnotations: () => MaybePromise>; }) { delete this.previewEntryError; @@ -311,11 +309,11 @@ export class Preview // main to be consistent with the previous behaviour. In the future, // we will change it to go ahead and load the story, which will end up being // "instant", although async. - renderStoryToElement(story: Store_Story, element: TStorybookRoot) { + renderStoryToElement(story: Store_Story, element: TFramework['rootElement']) { if (!this.renderToRoot) throw new Error(`Cannot call renderStoryToElement before initialization`); - const render = new StoryRender( + const render = new StoryRender( this.channel, this.storyStore, this.renderToRoot, @@ -335,9 +333,9 @@ export class Preview async teardownRender( render: - | StoryRender - | TemplateDocsRender - | StandaloneDocsRender, + | StoryRender + | TemplateDocsRender + | StandaloneDocsRender, { viewModeChanged }: { viewModeChanged?: boolean } = {} ) { this.storyRenders = this.storyRenders.filter((r) => r !== render); diff --git a/code/lib/preview-web/src/PreviewWeb.tsx b/code/lib/preview-web/src/PreviewWeb.tsx index b525e8282c9b..313ac3ba48b1 100644 --- a/code/lib/preview-web/src/PreviewWeb.tsx +++ b/code/lib/preview-web/src/PreviewWeb.tsx @@ -50,28 +50,25 @@ function focusInInput(event: Event) { return /input|textarea/i.test(target.tagName) || target.getAttribute('contenteditable') !== null; } -type PossibleRender = - | StoryRender - | TemplateDocsRender - | StandaloneDocsRender; - -function isStoryRender( - r: PossibleRender -): r is StoryRender { +type PossibleRender = + | StoryRender + | TemplateDocsRender + | StandaloneDocsRender; + +function isStoryRender( + r: PossibleRender +): r is StoryRender { return r.type === 'story'; } -export class PreviewWeb extends Preview< - TFramework, - TStorybookRoot -> { +export class PreviewWeb extends Preview { selectionStore: SelectionStore; - view: View; + view: View; currentSelection?: Store_Selection; - currentRender?: PossibleRender; + currentRender?: PossibleRender; constructor({ // I'm not quite sure how to express this -- if you don't pass a view, you need to ensure @@ -80,7 +77,7 @@ export class PreviewWeb; + view?: View; selectionStore?: SelectionStore; } = {}) { super(); @@ -99,9 +96,7 @@ export class PreviewWeb - ) { + initializeWithProjectAnnotations(projectAnnotations: ProjectAnnotations) { return super .initializeWithProjectAnnotations(projectAnnotations) .then(() => this.setInitialGlobals()); @@ -182,7 +177,7 @@ export class PreviewWeb MaybePromise>; + getProjectAnnotations: () => MaybePromise>; }) { await super.onGetProjectAnnotationsChanged({ getProjectAnnotations }); @@ -302,9 +297,9 @@ export class PreviewWeb; + let render: PossibleRender; if (entry.type === 'story') { - render = new StoryRender( + render = new StoryRender( this.channel, this.storyStore, (...args: Parameters) => { @@ -317,17 +312,9 @@ export class PreviewWeb( - this.channel, - this.storyStore, - entry - ); + render = new StandaloneDocsRender(this.channel, this.storyStore, entry); } else { - render = new TemplateDocsRender( - this.channel, - this.storyStore, - entry - ); + render = new TemplateDocsRender(this.channel, this.storyStore, entry); } // We need to store this right away, so if the story changes during @@ -406,8 +393,8 @@ export class PreviewWeb); - (this.currentRender as StoryRender).renderToElement( + this.storyRenders.push(render as StoryRender); + (this.currentRender as StoryRender).renderToElement( this.view.prepareForStory(render.story) ); } else { @@ -420,7 +407,7 @@ export class PreviewWeb, + render: PossibleRender, { viewModeChanged = false }: { viewModeChanged?: boolean } = {} ) { this.storyRenders = this.storyRenders.filter((r) => r !== render); diff --git a/code/lib/preview-web/src/docs-context/DocsContext.ts b/code/lib/preview-web/src/docs-context/DocsContext.ts index cdfdd9214e9f..687cac7295a1 100644 --- a/code/lib/preview-web/src/docs-context/DocsContext.ts +++ b/code/lib/preview-web/src/docs-context/DocsContext.ts @@ -13,9 +13,7 @@ import type { Channel } from '@storybook/channels'; import type { DocsContextProps } from './DocsContextProps'; -export class DocsContext - implements DocsContextProps -{ +export class DocsContext implements DocsContextProps { private componentStoriesValue: Store_Story[]; private storyIdToCSFFile: Map>; @@ -28,7 +26,7 @@ export class DocsContext, + protected store: StoryStore, public renderStoryToElement: DocsContextProps['renderStoryToElement'], /** The CSF files known (via the index) to be refererenced by this docs file */ csfFiles: Store_CSFFile[], diff --git a/code/lib/preview-web/src/render/Render.ts b/code/lib/preview-web/src/render/Render.ts index ecfaf6a9fc74..c7cd21be4d1c 100644 --- a/code/lib/preview-web/src/render/Render.ts +++ b/code/lib/preview-web/src/render/Render.ts @@ -9,15 +9,18 @@ export type RenderType = 'story' | 'docs'; * - Tracking the state of the rendering as it moves between preparing, rendering and tearing down. * - Tracking what is rendered to know if a change requires re-rendering or teardown + recreation. */ -export interface Render { +export interface Render { type: RenderType; id: StoryId; isPreparing: () => boolean; - isEqual: (other: Render) => boolean; + isEqual: (other: Render) => boolean; disableKeyListeners: boolean; teardown?: (options: { viewModeChanged: boolean }) => Promise; torndown: boolean; - renderToElement: (canvasElement: TStorybookRoot, renderStoryToElement?: any) => Promise; + renderToElement: ( + canvasElement: TFramework['rootElement'], + renderStoryToElement?: any + ) => Promise; } export const PREPARE_ABORTED = new Error('prepareAborted'); diff --git a/code/lib/preview-web/src/render/StandaloneDocsRender.ts b/code/lib/preview-web/src/render/StandaloneDocsRender.ts index 9f3fcd49d0ab..42d3b68e5e9b 100644 --- a/code/lib/preview-web/src/render/StandaloneDocsRender.ts +++ b/code/lib/preview-web/src/render/StandaloneDocsRender.ts @@ -24,9 +24,7 @@ import { DocsContext } from '../docs-context/DocsContext'; * - *.mdx file that may or may not reference a specific CSF file with `` */ -export class StandaloneDocsRender - implements Render -{ +export class StandaloneDocsRender implements Render { public readonly type: RenderType = 'docs'; public readonly id: StoryId; @@ -47,7 +45,7 @@ export class StandaloneDocsRender, + protected store: StoryStore, public entry: Addon_IndexEntry ) { this.id = entry.id; @@ -68,22 +66,22 @@ export class StandaloneDocsRender): boolean { + isEqual(other: Render): boolean { return !!( this.id === other.id && this.exports && - this.exports === (other as StandaloneDocsRender).exports + this.exports === (other as StandaloneDocsRender).exports ); } async renderToElement( - canvasElement: TStorybookRoot, + canvasElement: TFramework['rootElement'], renderStoryToElement: DocsContextProps['renderStoryToElement'] ) { if (!this.exports || !this.csfFiles || !this.store.projectAnnotations) throw new Error('Cannot render docs before preparing'); - const docsContext = new DocsContext( + const docsContext = new DocsContext( this.channel, this.store, renderStoryToElement, diff --git a/code/lib/preview-web/src/render/StoryRender.ts b/code/lib/preview-web/src/render/StoryRender.ts index 2480ab5690f8..d62e4da29924 100644 --- a/code/lib/preview-web/src/render/StoryRender.ts +++ b/code/lib/preview-web/src/render/StoryRender.ts @@ -47,9 +47,7 @@ export type RenderContextCallbacks = Pick< 'showMain' | 'showError' | 'showException' >; -export class StoryRender - implements Render -{ +export class StoryRender implements Render { public type: RenderType = 'story'; public story?: Store_Story; @@ -58,7 +56,7 @@ export class StoryRender, - private renderToScreen: RenderToRoot, + public store: StoryStore, + private renderToScreen: RenderToRoot, private callbacks: RenderContextCallbacks, public id: StoryId, public viewMode: ViewMode, @@ -112,11 +110,11 @@ export class StoryRender): boolean { + isEqual(other: Render): boolean { return !!( this.id === other.id && this.story && - this.story === (other as StoryRender).story + this.story === (other as StoryRender).story ); } @@ -128,7 +126,7 @@ export class StoryRender = { diff --git a/code/lib/preview-web/src/render/TemplateDocsRender.ts b/code/lib/preview-web/src/render/TemplateDocsRender.ts index 56462e1ff333..bc4af4f80944 100644 --- a/code/lib/preview-web/src/render/TemplateDocsRender.ts +++ b/code/lib/preview-web/src/render/TemplateDocsRender.ts @@ -27,9 +27,7 @@ import { DocsContext } from '../docs-context/DocsContext'; * - *.stories.mdx files, where the MDX compiler produces a CSF file with a `.parameter.docs.page` * parameter containing the compiled content of the MDX file. */ -export class TemplateDocsRender - implements Render -{ +export class TemplateDocsRender implements Render { public readonly type: RenderType = 'docs'; public readonly id: StoryId; @@ -50,7 +48,7 @@ export class TemplateDocsRender, + protected store: StoryStore, public entry: Addon_IndexEntry ) { this.id = entry.id; @@ -85,21 +83,21 @@ export class TemplateDocsRender): boolean { + isEqual(other: Render): boolean { return !!( this.id === other.id && this.story && - this.story === (other as TemplateDocsRender).story + this.story === (other as TemplateDocsRender).story ); } async renderToElement( - canvasElement: TStorybookRoot, + canvasElement: TFramework['rootElement'], renderStoryToElement: DocsContextProps['renderStoryToElement'] ) { if (!this.story || !this.csfFiles) throw new Error('Cannot render docs before preparing'); - const docsContext = new DocsContext( + const docsContext = new DocsContext( this.channel, this.store, renderStoryToElement, diff --git a/code/lib/store/src/StoryStore.ts b/code/lib/store/src/StoryStore.ts index 4dd65600c3c6..b353428e37e8 100644 --- a/code/lib/store/src/StoryStore.ts +++ b/code/lib/store/src/StoryStore.ts @@ -37,12 +37,12 @@ import { processCSFFile, prepareStory, normalizeProjectAnnotations } from './csf const CSF_CACHE_SIZE = 1000; const STORY_CACHE_SIZE = 10000; -export class StoryStore { +export class StoryStore { storyIndex?: StoryIndexStore; importFn?: Store_ModuleImportFn; - projectAnnotations?: Store_NormalizedProjectAnnotations; + projectAnnotations?: Store_NormalizedProjectAnnotations; globals?: GlobalsStore; @@ -77,7 +77,7 @@ export class StoryStore) { + setProjectAnnotations(projectAnnotations: ProjectAnnotations) { // By changing `this.projectAnnotations, we implicitly invalidate the `prepareStoryWithCache` this.projectAnnotations = normalizeProjectAnnotations(projectAnnotations); const { globals, globalTypes } = projectAnnotations; @@ -145,7 +145,7 @@ export class StoryStore['cachedCSFFiles']> { + loadAllCSFFiles(): Store_PromiseLike['cachedCSFFiles']> { if (!this.storyIndex) throw new Error(`loadAllCSFFiles called before initialization`); const importPaths: Record = {}; diff --git a/code/lib/store/src/csf/normalizeProjectAnnotations.ts b/code/lib/store/src/csf/normalizeProjectAnnotations.ts index dee69948569c..e4700f6d3fa7 100644 --- a/code/lib/store/src/csf/normalizeProjectAnnotations.ts +++ b/code/lib/store/src/csf/normalizeProjectAnnotations.ts @@ -9,15 +9,12 @@ import { inferArgTypes } from '../inferArgTypes'; import { inferControls } from '../inferControls'; import { normalizeInputTypes } from './normalizeInputTypes'; -export function normalizeProjectAnnotations({ +export function normalizeProjectAnnotations({ argTypes, globalTypes, argTypesEnhancers, ...annotations -}: ProjectAnnotations): Store_NormalizedProjectAnnotations< - TFramework, - TStorybookRoot -> { +}: ProjectAnnotations): Store_NormalizedProjectAnnotations { return { ...(argTypes && { argTypes: normalizeInputTypes(argTypes as ArgTypes) }), ...(globalTypes && { globalTypes: normalizeInputTypes(globalTypes) }), diff --git a/code/lib/store/src/csf/prepareStory.ts b/code/lib/store/src/csf/prepareStory.ts index b05167ec011c..c8db666bddf2 100644 --- a/code/lib/store/src/csf/prepareStory.ts +++ b/code/lib/store/src/csf/prepareStory.ts @@ -41,10 +41,10 @@ const argTypeDefaultValueWarning = deprecate( // // Note that this story function is *stateless* in the sense that it does not track args or globals // Instead, it is expected these are tracked separately (if necessary) and are passed into each invocation. -export function prepareStory( +export function prepareStory( storyAnnotations: Store_NormalizedStoryAnnotations, componentAnnotations: Store_NormalizedComponentAnnotations, - projectAnnotations: Store_NormalizedProjectAnnotations + projectAnnotations: Store_NormalizedProjectAnnotations ): Store_Story { // NOTE: in the current implementation we are doing everything once, up front, rather than doing // anything at render time. The assumption is that as we don't load all the stories at once, this diff --git a/code/lib/types/src/modules/store.ts b/code/lib/types/src/modules/store.ts index 7e837c4ac518..91dcc83ca9a0 100644 --- a/code/lib/types/src/modules/store.ts +++ b/code/lib/types/src/modules/store.ts @@ -43,28 +43,23 @@ export type Store_ModuleImportFn = (path: Path) => Store_PromiseLike = Promise | T; export type TeardownRenderToRoot = () => Store_MaybePromise; -export type RenderToRoot = ( +export type RenderToRoot = ( context: Store_RenderContext, - element: TStorybookRoot + element: TFramework['rootElement'] ) => Store_MaybePromise; -export type ProjectAnnotations< - TFramework extends Framework, - TStorybookRoot = HTMLElement -> = CsfProjectAnnotations & { - renderToRoot?: RenderToRoot; +export type ProjectAnnotations = CsfProjectAnnotations & { + renderToRoot?: RenderToRoot; /* @deprecated use renderToRoot */ - renderToDOM?: RenderToRoot; + renderToDOM?: RenderToRoot; }; -export type Store_NormalizedProjectAnnotations< - TFramework extends Framework = Framework, - TStorybookRoot = HTMLElement -> = ProjectAnnotations & { - argTypes?: StrictArgTypes; - globalTypes?: StrictGlobalTypes; -}; +export type Store_NormalizedProjectAnnotations = + ProjectAnnotations & { + argTypes?: StrictArgTypes; + globalTypes?: StrictGlobalTypes; + }; export type Store_NormalizedComponentAnnotations = ComponentAnnotations & {