import type { ConcreteComponent, Component, ComponentOptions, App } from 'vue'; import { h } from 'vue'; import { start } from '@storybook/core/client'; import { ClientStoryApi, StoryFn, DecoratorFunction, StoryContext, Loadable, } from '@storybook/addons'; import './globals'; import { IStorybookSection, StoryFnVueReturnType } from './types'; import render, { storybookApp } from './render'; /* This normalizes a functional component into a render method in ComponentOptions. The concept is taken from Vue 3's `defineComponent` but changed from creating a `setup` method on the ComponentOptions so end-users don't need to specify a "thunk" as a decorator. */ function normalizeFunctionalComponent(options: ConcreteComponent): ComponentOptions { return typeof options === 'function' ? { render: options, name: options.name } : options; } function prepare(rawStory: StoryFnVueReturnType, innerStory?: ConcreteComponent): Component | null { const story = rawStory as ComponentOptions; if (story == null) { return null; } if (innerStory) { return { // Normalize so we can always spread an object ...normalizeFunctionalComponent(story), components: { ...(story.components || {}), story: innerStory }, }; } return { render() { return h(story); }, }; } const defaultContext: StoryContext = { id: 'unspecified', name: 'unspecified', kind: 'unspecified', parameters: {}, args: {}, argTypes: {}, globals: {}, }; function decorateStory( storyFn: StoryFn, decorators: DecoratorFunction[] ): StoryFn { return decorators.reduce( (decorated: StoryFn, decorator) => ( context: StoryContext = defaultContext ) => { let story; const decoratedStory = decorator( ({ parameters, ...innerContext }: StoryContext = {} as StoryContext) => { story = decorated({ ...context, ...innerContext }); return story; }, context ); if (!story) { story = decorated(context); } if (decoratedStory === story) { return story; } return prepare(decoratedStory, story); }, (context) => prepare(storyFn(context)) ); } const framework = 'vue3'; interface ClientApi extends ClientStoryApi { setAddon(addon: any): void; configure(loader: Loadable, module: NodeModule): void; getStorybook(): IStorybookSection[]; clearDecorators(): void; forceReRender(): void; raw: () => any; // todo add type load: (...args: any[]) => void; app: App; } const api = start(render, { decorateStory }); export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ framework, }); }; export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); export const { addDecorator } = api.clientApi; export const { addParameters } = api.clientApi; export const { clearDecorators } = api.clientApi; export const { setAddon } = api.clientApi; export const { forceReRender } = api; export const { getStorybook } = api.clientApi; export const { raw } = api.clientApi; export const app: ClientApi['app'] = storybookApp; export { activeStoryComponent } from './render';