UNPKG

storybook

Version:

Storybook: Develop, document, and test UI components in isolation

1,202 lines (1,161 loc) • 129 kB
import { isTestEnvironment, pauseAnimations, waitForAnimations } from "./chunk-MEXTPDJG.js"; import { Tag } from "./chunk-2GLGVABT.js"; import { require_main } from "./chunk-XLJZ7AOP.js"; import { SNIPPET_RENDERED, combineParameters } from "./chunk-SI6AKD4S.js"; import { isEqual } from "./chunk-IYCKG66Y.js"; import { invariant } from "./chunk-IWQGIXJS.js"; import { require_ansi_to_html } from "./chunk-V2VKKSMQ.js"; import { mapValues, pickBy } from "./chunk-YK43Z22A.js"; import { isPlainObject } from "./chunk-LCHBOIHN.js"; import { require_memoizerific } from "./chunk-ZCFV7BZB.js"; import { dedent } from "./chunk-3LY4VQVK.js"; import { __toESM } from "./chunk-A242L54C.js"; // src/preview-api/modules/addons/main.ts import { global } from "@storybook/global"; // src/preview-api/modules/addons/storybook-channel-mock.ts import { Channel } from "storybook/internal/channels"; function mockChannel() { let transport = { setHandler: () => { }, send: () => { } }; return new Channel({ transport }); } // src/preview-api/modules/addons/main.ts var AddonStore = class { constructor() { this.getChannel = () => { if (!this.channel) { let channel = mockChannel(); return this.setChannel(channel), channel; } return this.channel; }; this.ready = () => this.promise; this.hasChannel = () => !!this.channel; this.setChannel = (channel) => { this.channel = channel, this.resolve(); }; this.promise = new Promise((res) => { this.resolve = () => res(this.getChannel()); }); } }, KEY = "__STORYBOOK_ADDONS_PREVIEW"; function getAddonsStore() { return global[KEY] || (global[KEY] = new AddonStore()), global[KEY]; } var addons = getAddonsStore(); // src/preview-api/modules/addons/hooks.ts import { logger } from "storybook/internal/client-logger"; import { FORCE_RE_RENDER, RESET_STORY_ARGS, STORY_RENDERED, UPDATE_GLOBALS, UPDATE_STORY_ARGS } from "storybook/internal/core-events"; import { global as global2 } from "@storybook/global"; var HooksContext = class { constructor() { this.hookListsMap = void 0; this.mountedDecorators = void 0; this.prevMountedDecorators = void 0; this.currentHooks = void 0; this.nextHookIndex = void 0; this.currentPhase = void 0; this.currentEffects = void 0; this.prevEffects = void 0; this.currentDecoratorName = void 0; this.hasUpdates = void 0; this.currentContext = void 0; this.renderListener = (storyId) => { storyId === this.currentContext?.id && (this.triggerEffects(), this.currentContext = null, this.removeRenderListeners()); }; this.init(); } init() { this.hookListsMap = /* @__PURE__ */ new WeakMap(), this.mountedDecorators = /* @__PURE__ */ new Set(), this.prevMountedDecorators = /* @__PURE__ */ new Set(), this.currentHooks = [], this.nextHookIndex = 0, this.currentPhase = "NONE", this.currentEffects = [], this.prevEffects = [], this.currentDecoratorName = null, this.hasUpdates = !1, this.currentContext = null; } clean() { this.prevEffects.forEach((effect) => { effect.destroy && effect.destroy(); }), this.init(), this.removeRenderListeners(); } getNextHook() { let hook = this.currentHooks[this.nextHookIndex]; return this.nextHookIndex += 1, hook; } triggerEffects() { this.prevEffects.forEach((effect) => { !this.currentEffects.includes(effect) && effect.destroy && effect.destroy(); }), this.currentEffects.forEach((effect) => { this.prevEffects.includes(effect) || (effect.destroy = effect.create()); }), this.prevEffects = this.currentEffects, this.currentEffects = []; } addRenderListeners() { this.removeRenderListeners(), addons.getChannel().on(STORY_RENDERED, this.renderListener); } removeRenderListeners() { addons.getChannel().removeListener(STORY_RENDERED, this.renderListener); } }; function hookify(fn) { let hookified = (...args) => { let { hooks } = typeof args[0] == "function" ? args[1] : args[0], prevPhase = hooks.currentPhase, prevHooks = hooks.currentHooks, prevNextHookIndex = hooks.nextHookIndex, prevDecoratorName = hooks.currentDecoratorName; hooks.currentDecoratorName = fn.name, hooks.prevMountedDecorators.has(fn) ? (hooks.currentPhase = "UPDATE", hooks.currentHooks = hooks.hookListsMap.get(fn) || []) : (hooks.currentPhase = "MOUNT", hooks.currentHooks = [], hooks.hookListsMap.set(fn, hooks.currentHooks), hooks.prevMountedDecorators.add(fn)), hooks.nextHookIndex = 0; let prevContext = global2.STORYBOOK_HOOKS_CONTEXT; global2.STORYBOOK_HOOKS_CONTEXT = hooks; let result = fn(...args); if (global2.STORYBOOK_HOOKS_CONTEXT = prevContext, hooks.currentPhase === "UPDATE" && hooks.getNextHook() != null) throw new Error( "Rendered fewer hooks than expected. This may be caused by an accidental early return statement." ); return hooks.currentPhase = prevPhase, hooks.currentHooks = prevHooks, hooks.nextHookIndex = prevNextHookIndex, hooks.currentDecoratorName = prevDecoratorName, result; }; return hookified.originalFn = fn, hookified; } var numberOfRenders = 0, RENDER_LIMIT = 25, applyHooks = (applyDecorators) => (storyFn, decorators) => { let decorated = applyDecorators( hookify(storyFn), decorators.map((decorator) => hookify(decorator)) ); return (context) => { let { hooks } = context; hooks.prevMountedDecorators ??= /* @__PURE__ */ new Set(), hooks.mountedDecorators = /* @__PURE__ */ new Set([storyFn, ...decorators]), hooks.currentContext = context, hooks.hasUpdates = !1; let result = decorated(context); for (numberOfRenders = 1; hooks.hasUpdates; ) if (hooks.hasUpdates = !1, hooks.currentEffects = [], result = decorated(context), numberOfRenders += 1, numberOfRenders > RENDER_LIMIT) throw new Error( "Too many re-renders. Storybook limits the number of renders to prevent an infinite loop." ); return hooks.addRenderListeners(), result; }; }, areDepsEqual = (deps, nextDeps) => deps.length === nextDeps.length && deps.every((dep, i) => dep === nextDeps[i]), invalidHooksError = () => new Error( `Storybook preview hooks can only be called inside decorators and story functions. When combining Storybook hooks (e.g. useArgs) with framework hooks (e.g. React's useState, useEffect, useRef) in the same render function, use Storybook's equivalents from 'storybook/preview-api' instead: useState, useEffect, useRef, useMemo, useCallback, useReducer.` ); function getHooksContextOrNull() { return global2.STORYBOOK_HOOKS_CONTEXT || null; } function getHooksContextOrThrow() { let hooks = getHooksContextOrNull(); if (hooks == null) throw invalidHooksError(); return hooks; } function useHook(name, callback, deps) { let hooks = getHooksContextOrThrow(); if (hooks.currentPhase === "MOUNT") { deps != null && !Array.isArray(deps) && logger.warn( `${name} received a final argument that is not an array (instead, received ${deps}). When specified, the final argument must be an array.` ); let hook = { name, deps }; return hooks.currentHooks.push(hook), callback(hook), hook; } if (hooks.currentPhase === "UPDATE") { let hook = hooks.getNextHook(); if (hook == null) throw new Error("Rendered more hooks than during the previous render."); return hook.name !== name && logger.warn( `Storybook has detected a change in the order of Hooks${hooks.currentDecoratorName ? ` called by ${hooks.currentDecoratorName}` : ""}. This will lead to bugs and errors if not fixed.` ), deps != null && hook.deps == null && logger.warn( `${name} received a final argument during this render, but not during the previous render. Even though the final argument is optional, its type cannot change between renders.` ), deps != null && hook.deps != null && deps.length !== hook.deps.length && logger.warn(`The final argument passed to ${name} changed size between renders. The order and size of this array must remain constant. Previous: ${hook.deps} Incoming: ${deps}`), (deps == null || hook.deps == null || !areDepsEqual(deps, hook.deps)) && (callback(hook), hook.deps = deps), hook; } throw invalidHooksError(); } function useMemoLike(name, nextCreate, deps) { let { memoizedState } = useHook( name, (hook) => { hook.memoizedState = nextCreate(); }, deps ); return memoizedState; } function useMemo(nextCreate, deps) { return useMemoLike("useMemo", nextCreate, deps); } function useCallback(callback, deps) { return useMemoLike("useCallback", () => callback, deps); } function useRefLike(name, initialValue) { return useMemoLike(name, () => ({ current: initialValue }), []); } function useRef(initialValue) { return useRefLike("useRef", initialValue); } function triggerUpdate() { let hooks = getHooksContextOrNull(); if (hooks != null && hooks.currentPhase !== "NONE") hooks.hasUpdates = !0; else try { addons.getChannel().emit(FORCE_RE_RENDER); } catch { logger.warn("State updates of Storybook preview hooks work only in browser"); } } function useStateLike(name, initialState) { let stateRef = useRefLike( name, // @ts-expect-error S type should never be function, but there's no way to tell that to TypeScript typeof initialState == "function" ? initialState() : initialState ), setState = (update) => { stateRef.current = typeof update == "function" ? update(stateRef.current) : update, triggerUpdate(); }; return [stateRef.current, setState]; } function useState(initialState) { return useStateLike("useState", initialState); } function useReducer(reducer, initialArg, init) { let initialState = init != null ? () => init(initialArg) : initialArg, [state, setState] = useStateLike("useReducer", initialState); return [state, (action) => setState((prevState) => reducer(prevState, action))]; } function useEffect(create, deps) { let hooks = getHooksContextOrThrow(), effect = useMemoLike("useEffect", () => ({ create }), deps); hooks.currentEffects.includes(effect) || hooks.currentEffects.push(effect); } function useChannel(eventMap, deps = []) { let channel = addons.getChannel(); return useEffect(() => (Object.entries(eventMap).forEach(([type, listener]) => channel.on(type, listener)), () => { Object.entries(eventMap).forEach( ([type, listener]) => channel.removeListener(type, listener) ); }), [...Object.keys(eventMap), ...deps]), useCallback(channel.emit.bind(channel), [channel]); } function useStoryContext() { let { currentContext } = getHooksContextOrThrow(); if (currentContext == null) throw invalidHooksError(); return currentContext; } function useParameter(parameterKey, defaultValue) { let { parameters } = useStoryContext(); if (parameterKey) return parameters[parameterKey] ?? defaultValue; } function useArgs() { let channel = addons.getChannel(), { id: storyId, args } = useStoryContext(), updateArgs = useCallback( (updatedArgs) => channel.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs }), [channel, storyId] ), resetArgs = useCallback( (argNames) => channel.emit(RESET_STORY_ARGS, { storyId, argNames }), [channel, storyId] ); return [args, updateArgs, resetArgs]; } function useGlobals() { let channel = addons.getChannel(), { globals } = useStoryContext(), updateGlobals = useCallback( (newGlobals) => channel.emit(UPDATE_GLOBALS, { globals: newGlobals }), [channel] ); return [globals, updateGlobals]; } // src/preview-api/modules/addons/make-decorator.ts var makeDecorator = ({ name, parameterName, wrapper, skipIfNoParametersOrOptions = !1 }) => { let decorator = (options) => (storyFn, context) => { let parameters = context.parameters && context.parameters[parameterName]; return parameters && parameters.disable || skipIfNoParametersOrOptions && !options && !parameters ? storyFn(context) : wrapper(storyFn, context, { options, parameters }); }; return (...args) => typeof args[0] == "function" ? decorator()(...args) : (...innerArgs) => { if (innerArgs.length > 1) return args.length > 1 ? decorator(args)(...innerArgs) : decorator(...args)(...innerArgs); throw new Error( `Passing stories directly into ${name}() is not allowed, instead use addDecorator(${name}) and pass options with the '${parameterName}' parameter` ); }; }; // src/preview-api/modules/store/StoryStore.ts import { getCoreAnnotations as getCoreAnnotations2 } from "storybook/internal/csf"; import { CalledExtractOnStoreError, MissingStoryFromCsfFileError } from "storybook/internal/preview-errors"; var import_memoizerific2 = __toESM(require_memoizerific(), 1); // src/preview-api/modules/store/args.ts import { once } from "storybook/internal/client-logger"; var INCOMPATIBLE = Symbol("incompatible"), map = (arg, argType) => { let type = argType.type; if (arg == null || !type || argType.mapping) return arg; switch (type.name) { case "string": return String(arg); case "enum": return arg; case "number": return Number(arg); case "boolean": return String(arg) === "true"; case "array": return !type.value || !Array.isArray(arg) ? INCOMPATIBLE : arg.reduce((acc, item, index) => { let mapped = map(item, { type: type.value }); return mapped !== INCOMPATIBLE && (acc[index] = mapped), acc; }, new Array(arg.length)); case "object": return typeof arg == "string" || typeof arg == "number" ? arg : !type.value || typeof arg != "object" ? INCOMPATIBLE : Object.entries(arg).reduce((acc, [key, val]) => { let mapped = map(val, { type: type.value[key] }); return mapped === INCOMPATIBLE ? acc : Object.assign(acc, { [key]: mapped }); }, {}); case "other": { let isPrimitiveArg = typeof arg == "string" || typeof arg == "number" || typeof arg == "boolean"; return type.value === "ReactNode" && isPrimitiveArg ? arg : INCOMPATIBLE; } default: return INCOMPATIBLE; } }, mapArgsToTypes = (args, argTypes) => Object.entries(args).reduce((acc, [key, value]) => { if (!argTypes[key]) return acc; let mapped = map(value, argTypes[key]); return mapped === INCOMPATIBLE ? acc : Object.assign(acc, { [key]: mapped }); }, {}), combineArgs = (value, update) => Array.isArray(value) && Array.isArray(update) ? update.reduce( (acc, upd, index) => (acc[index] = combineArgs(value[index], update[index]), acc), [...value] ).filter((v) => v !== void 0) : !isPlainObject(value) || !isPlainObject(update) ? update : Object.keys({ ...value, ...update }).reduce((acc, key) => { if (key in update) { let combined = combineArgs(value[key], update[key]); combined !== void 0 && (acc[key] = combined); } else acc[key] = value[key]; return acc; }, {}), validateOptions = (args, argTypes) => Object.entries(argTypes).reduce((acc, [key, { options }]) => { function allowArg() { return key in args && (acc[key] = args[key]), acc; } if (!options) return allowArg(); if (!Array.isArray(options)) return once.error(dedent` Invalid argType: '${key}.options' should be an array. More info: https://storybook.js.org/docs/api/arg-types?ref=error `), allowArg(); if (options.some((opt) => opt && ["object", "function"].includes(typeof opt))) return once.error(dedent` Invalid argType: '${key}.options' should only contain primitives. Use a 'mapping' for complex values. More info: https://storybook.js.org/docs/writing-stories/args?ref=error#mapping-to-complex-arg-values `), allowArg(); let isArray = Array.isArray(args[key]), invalidIndex = isArray && args[key].findIndex((val) => !options.includes(val)), isValidArray = isArray && invalidIndex === -1; if (args[key] === void 0 || options.includes(args[key]) || isValidArray) return allowArg(); let field = isArray ? `${key}[${invalidIndex}]` : key, supportedOptions = options.map((opt) => typeof opt == "string" ? `'${opt}'` : String(opt)).join(", "); return once.warn(`Received illegal value for '${field}'. Supported options: ${supportedOptions}`), acc; }, {}), DEEPLY_EQUAL = Symbol("Deeply equal"), deepDiff = (value, update) => { if (typeof value != typeof update) return update; if (isEqual(value, update)) return DEEPLY_EQUAL; if (Array.isArray(value) && Array.isArray(update)) { let res = update.reduce((acc, upd, index) => { let diff = deepDiff(value[index], upd); return diff !== DEEPLY_EQUAL && (acc[index] = diff), acc; }, new Array(update.length)); return update.length >= value.length ? res : res.concat(new Array(value.length - update.length).fill(void 0)); } return isPlainObject(value) && isPlainObject(update) ? Object.keys({ ...value, ...update }).reduce((acc, key) => { let diff = deepDiff(value?.[key], update?.[key]); return diff === DEEPLY_EQUAL ? acc : Object.assign(acc, { [key]: diff }); }, {}) : update; }, UNTARGETED = "UNTARGETED"; function groupArgsByTarget({ args, argTypes }) { let groupedArgs = {}; return Object.entries(args).forEach(([name, value]) => { let { target = UNTARGETED } = argTypes[name] || {}; groupedArgs[target] = groupedArgs[target] || {}, groupedArgs[target][name] = value; }), groupedArgs; } // src/preview-api/modules/store/ArgsStore.ts function deleteUndefined(obj) { return Object.keys(obj).forEach((key) => obj[key] === void 0 && delete obj[key]), obj; } var ArgsStore = class { constructor() { this.initialArgsByStoryId = {}; this.argsByStoryId = {}; } get(storyId) { if (!(storyId in this.argsByStoryId)) throw new Error(`No args known for ${storyId} -- has it been rendered yet?`); return this.argsByStoryId[storyId]; } setInitial(story) { if (!this.initialArgsByStoryId[story.id]) this.initialArgsByStoryId[story.id] = story.initialArgs, this.argsByStoryId[story.id] = story.initialArgs; else if (this.initialArgsByStoryId[story.id] !== story.initialArgs) { let delta = deepDiff(this.initialArgsByStoryId[story.id], this.argsByStoryId[story.id]); this.initialArgsByStoryId[story.id] = story.initialArgs, this.argsByStoryId[story.id] = story.initialArgs, delta !== DEEPLY_EQUAL && this.updateFromDelta(story, delta); } } updateFromDelta(story, delta) { let validatedDelta = validateOptions(delta, story.argTypes); this.argsByStoryId[story.id] = combineArgs(this.argsByStoryId[story.id], validatedDelta); } updateFromPersisted(story, persisted) { let mappedPersisted = mapArgsToTypes(persisted, story.argTypes); return this.updateFromDelta(story, mappedPersisted); } update(storyId, argsUpdate) { if (!(storyId in this.argsByStoryId)) throw new Error(`No args known for ${storyId} -- has it been rendered yet?`); this.argsByStoryId[storyId] = deleteUndefined({ ...this.argsByStoryId[storyId], ...argsUpdate }); } }; // src/preview-api/modules/store/GlobalsStore.ts import { logger as logger2 } from "storybook/internal/client-logger"; // src/preview-api/modules/store/csf/getValuesFromGlobalTypes.ts var getValuesFromGlobalTypes = (globalTypes = {}) => Object.entries(globalTypes).reduce((acc, [arg, { defaultValue }]) => (typeof defaultValue < "u" && (acc[arg] = defaultValue), acc), {}); // src/preview-api/modules/store/GlobalsStore.ts var GlobalsStore = class { constructor({ globals = {}, globalTypes = {} }) { this.set({ globals, globalTypes }); } set({ globals = {}, globalTypes = {} }) { let delta = this.initialGlobals && deepDiff(this.initialGlobals, this.globals); this.allowedGlobalNames = /* @__PURE__ */ new Set([...Object.keys(globals), ...Object.keys(globalTypes)]); let defaultGlobals = getValuesFromGlobalTypes(globalTypes); this.initialGlobals = { ...defaultGlobals, ...globals }, this.globals = this.initialGlobals, delta && delta !== DEEPLY_EQUAL && this.updateFromPersisted(delta); } filterAllowedGlobals(globals) { return Object.entries(globals).reduce((acc, [key, value]) => (this.allowedGlobalNames.has(key) ? acc[key] = value : logger2.warn( `Attempted to set a global (${key}) that is not defined in initial globals or globalTypes` ), acc), {}); } updateFromPersisted(persisted) { let allowedUrlGlobals = this.filterAllowedGlobals(persisted); this.globals = { ...this.globals, ...allowedUrlGlobals }; } get() { return this.globals; } update(newGlobals) { this.globals = { ...this.globals, ...this.filterAllowedGlobals(newGlobals) }; for (let key in newGlobals) newGlobals[key] === void 0 && (this.globals[key] = this.initialGlobals[key]); } }; // src/preview-api/modules/store/StoryIndexStore.ts var import_memoizerific = __toESM(require_memoizerific(), 1); import { MissingStoryAfterHmrError } from "storybook/internal/preview-errors"; var getImportPathMap = (0, import_memoizerific.default)(1)( (entries) => Object.values(entries).reduce( (acc, entry) => (acc[entry.importPath] = acc[entry.importPath] || entry, acc), {} ) ), StoryIndexStore = class { constructor({ entries } = { v: 5, entries: {} }) { this.entries = entries; } entryFromSpecifier(specifier) { let entries = Object.values(this.entries); if (specifier === "*") return entries[0]; if (typeof specifier == "string") return this.entries[specifier] ? this.entries[specifier] : entries.find((entry) => entry.id.startsWith(specifier)); let { name, title } = specifier; return entries.find((entry) => entry.name === name && entry.title === title); } storyIdToEntry(storyId) { let storyEntry = this.entries[storyId]; if (!storyEntry) throw new MissingStoryAfterHmrError({ storyId }); return storyEntry; } importPathToEntry(importPath) { return getImportPathMap(this.entries)[importPath]; } }; // src/preview-api/modules/store/csf/normalizeInputTypes.ts var normalizeType = (type) => typeof type == "string" ? { name: type } : type, normalizeControl = (control) => typeof control == "string" ? { type: control, disable: !1 } : control && typeof control == "object" && "type" in control && !("disable" in control) ? { ...control, disable: !1 } : control, normalizeInputType = (inputType, key) => { let { type, control, ...rest } = inputType, normalized = { name: key, ...rest }; return type && (normalized.type = normalizeType(type)), control ? normalized.control = normalizeControl(control) : control === !1 && (normalized.control = { disable: !0 }), normalized; }, normalizeInputTypes = (inputTypes) => mapValues(inputTypes, normalizeInputType); // src/preview-api/modules/store/csf/normalizeStory.ts import { deprecate, logger as logger3 } from "storybook/internal/client-logger"; import { storyNameFromExport, toId } from "storybook/internal/csf"; // src/preview-api/modules/store/csf/normalizeArrays.ts var normalizeArrays = (array) => Array.isArray(array) ? array : array ? [array] : []; // src/preview-api/modules/store/csf/normalizeStory.ts var deprecatedStoryAnnotation = dedent` CSF .story annotations deprecated; annotate story functions directly: - StoryFn.story.name => StoryFn.storyName - StoryFn.story.(parameters|decorators) => StoryFn.(parameters|decorators) See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#hoisted-csf-annotations for details and codemod. `; function normalizeStory(key, storyAnnotations, meta) { let storyObject = storyAnnotations, userStoryFn = typeof storyAnnotations == "function" ? storyAnnotations : null, { story } = storyObject; story && (logger3.debug("deprecated story", story), deprecate(deprecatedStoryAnnotation)); let exportName = storyNameFromExport(key), name = typeof storyObject != "function" && storyObject.name || storyObject.storyName || story?.name || exportName, decorators = [ ...normalizeArrays(storyObject.decorators), ...normalizeArrays(story?.decorators) ], parameters = { ...story?.parameters, ...storyObject.parameters }, args = { ...story?.args, ...storyObject.args }, argTypes = { ...story?.argTypes, ...storyObject.argTypes }, loaders = [...normalizeArrays(storyObject.loaders), ...normalizeArrays(story?.loaders)], beforeEach = [ ...normalizeArrays(storyObject.beforeEach), ...normalizeArrays(story?.beforeEach) ], afterEach = [ ...normalizeArrays(storyObject.afterEach), ...normalizeArrays(story?.afterEach) ], { render, play, tags = [], globals = {} } = storyObject, id = parameters.__id || toId(meta.id, exportName); return { moduleExport: storyAnnotations, id, name, tags, decorators, parameters, args, argTypes: normalizeInputTypes(argTypes), loaders, beforeEach, afterEach, globals, ...render && { render }, ...userStoryFn && { userStoryFn }, ...play && { play } }; } // src/preview-api/modules/store/csf/processCSFFile.ts import { logger as logger4 } from "storybook/internal/client-logger"; import { getStoryChildren, isExportStory, isStory, toTestId } from "storybook/internal/csf"; // src/preview-api/modules/store/csf/normalizeComponentAnnotations.ts import { sanitize } from "storybook/internal/csf"; function normalizeComponentAnnotations(defaultExport, title = defaultExport.title, importPath) { let { id, argTypes } = defaultExport; return { id: sanitize(id || title), ...defaultExport, title, ...argTypes && { argTypes: normalizeInputTypes(argTypes) }, parameters: { fileName: importPath, ...defaultExport.parameters } }; } // src/preview-api/modules/store/csf/processCSFFile.ts var checkGlobals = (parameters) => { let { globals, globalTypes } = parameters; (globals || globalTypes) && logger4.error( "Global args/argTypes can only be set globally", JSON.stringify({ globals, globalTypes }) ); }, checkStorySort = (parameters) => { let { options } = parameters; options?.storySort && logger4.error("The storySort option parameter can only be set globally"); }, checkDisallowedParameters = (parameters) => { parameters && (checkGlobals(parameters), checkStorySort(parameters)); }; function processCSFFile(moduleExports, importPath, title) { let { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports, factoryStory = Object.values(namedExports).find((it) => isStory(it)); if (factoryStory) { let meta2 = normalizeComponentAnnotations(factoryStory.meta.input, title, importPath); checkDisallowedParameters(meta2.parameters); let csfFile2 = { meta: meta2, stories: {}, moduleExports }; return Object.keys(namedExports).forEach((key) => { if (isExportStory(key, meta2) && isStory(namedExports[key])) { let story = namedExports[key], storyMeta = normalizeStory(key, story.input, meta2); checkDisallowedParameters(storyMeta.parameters), csfFile2.stories[storyMeta.id] = storyMeta, getStoryChildren(story).forEach((child) => { let name = child.input.name, childId = toTestId(storyMeta.id, name); child.input.parameters ??= {}, child.input.parameters.__id = childId, csfFile2.stories[childId] = normalizeStory(name, child.input, meta2); }); } }), csfFile2.projectAnnotations = factoryStory.meta.preview.composed, csfFile2; } let meta = normalizeComponentAnnotations( defaultExport, title, importPath ); checkDisallowedParameters(meta.parameters); let csfFile = { meta, stories: {}, moduleExports }; return Object.keys(namedExports).forEach((key) => { if (isExportStory(key, meta)) { let storyMeta = normalizeStory(key, namedExports[key], meta); checkDisallowedParameters(storyMeta.parameters), csfFile.stories[storyMeta.id] = storyMeta; } }), csfFile; } // src/preview-api/modules/store/csf/prepareStory.ts import { combineTags, includeConditionalArg } from "storybook/internal/csf"; import { NoRenderFunctionError } from "storybook/internal/preview-errors"; import { global as global3 } from "@storybook/global"; import { global as globalThis2 } from "@storybook/global"; // src/preview-api/modules/preview-web/render/mount-utils.ts function mountDestructured(playFunction) { return playFunction != null && getUsedProps(playFunction).includes("mount"); } function getUsedProps(fn) { let [, args, body] = fn.toString().match(/[^(]*\(([^)]+)\)(?:.*{([^]+)})?/) || []; if (!args) return []; let [firstArg] = splitByComma(args); if (!firstArg) return []; let [, destructuredProps] = firstArg.match(/^{([^]+)}$/) || []; if (destructuredProps) return splitByComma(stripComments(destructuredProps)).map( (prop) => prop.replace(/:.*|=.*/g, "").trim() ); if (!firstArg.match(/^[a-z_$][0-9a-z_$]*$/i)) return []; let escapedArg = firstArg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), [, destructuredArg] = body?.trim()?.match(new RegExp(`^(?:const|let|var)\\s*{([^}]+)}\\s*=\\s*${escapedArg};`)) || []; return destructuredArg ? splitByComma(stripComments(destructuredArg)).map( (prop) => prop.replace(/:.*|=.*/g, "").trim() ) : []; } function stripComments(s) { return s = s.replace(/\/\/.*$/gm, ""), s = s.replace(/\/\*[\s\S]*?\*\//g, ""), s; } function splitByComma(s) { let result = [], stack = [], start = 0; for (let i = 0; i < s.length; i++) if (s[i] === "{" || s[i] === "[") stack.push(s[i] === "{" ? "}" : "]"); else if (s[i] === stack[stack.length - 1]) stack.pop(); else if (!stack.length && s[i] === ",") { let token = s.substring(start, i).trim(); token && result.push(token), start = i + 1; } let lastToken = s.substring(start).trim(); return lastToken && result.push(lastToken), result; } // src/preview-api/modules/store/decorators.ts function decorateStory(storyFn, decorator, bindWithContext) { let boundStoryFunction = bindWithContext(storyFn); return (context) => decorator(boundStoryFunction, context); } function sanitizeStoryContextUpdate({ componentId, title, kind, id, name, story, parameters, initialArgs, argTypes, ...update } = {}) { return update; } function defaultDecorateStory(storyFn, decorators) { let contextStore = {}, bindWithContext = (decoratedStoryFn) => (update) => { if (!contextStore.value) throw new Error("Decorated function called without init"); return contextStore.value = { ...contextStore.value, ...sanitizeStoryContextUpdate(update) }, decoratedStoryFn(contextStore.value); }, decoratedWithContextStore = decorators.reduce( (story, decorator) => decorateStory(story, decorator, bindWithContext), storyFn ); return (context) => (contextStore.value = context, decoratedWithContextStore(context)); } // src/preview-api/modules/store/csf/prepareStory.ts function prepareStory(storyAnnotations, componentAnnotations, projectAnnotations) { let { moduleExport, id, name } = storyAnnotations || {}, partialAnnotations = preparePartialAnnotations( storyAnnotations, componentAnnotations, projectAnnotations ), applyLoaders = async (context) => { let loaded = {}; for (let loaders of [ normalizeArrays(projectAnnotations.loaders), normalizeArrays(componentAnnotations.loaders), normalizeArrays(storyAnnotations.loaders) ]) { if (context.abortSignal.aborted) return loaded; let loadResults = await Promise.all(loaders.map((loader) => loader(context))); Object.assign(loaded, ...loadResults); } return loaded; }, applyBeforeEach = async (context) => { let cleanupCallbacks = new Array(); for (let beforeEach of [ ...normalizeArrays(projectAnnotations.beforeEach), ...normalizeArrays(componentAnnotations.beforeEach), ...normalizeArrays(storyAnnotations.beforeEach) ]) { if (context.abortSignal.aborted) return cleanupCallbacks; let cleanup = await beforeEach(context); cleanup && cleanupCallbacks.push(cleanup); } return cleanupCallbacks; }, applyAfterEach = async (context) => { let reversedFinalizers = [ ...normalizeArrays(projectAnnotations.afterEach), ...normalizeArrays(componentAnnotations.afterEach), ...normalizeArrays(storyAnnotations.afterEach) ].reverse(); for (let finalizer of reversedFinalizers) { if (context.abortSignal.aborted) return; await finalizer(context); } }, undecoratedStoryFn = (context) => context.originalStoryFn(context.args, context), { applyDecorators = defaultDecorateStory, runStep } = projectAnnotations, decorators = [ ...normalizeArrays(storyAnnotations?.decorators), ...normalizeArrays(componentAnnotations?.decorators), ...normalizeArrays(projectAnnotations?.decorators) ], render = storyAnnotations?.userStoryFn || storyAnnotations?.render || componentAnnotations.render || projectAnnotations.render, decoratedStoryFn = applyHooks(applyDecorators)(undecoratedStoryFn, decorators), unboundStoryFn = (context) => decoratedStoryFn(context), playFunction = storyAnnotations?.play ?? componentAnnotations?.play, usesMount = mountDestructured(playFunction); if (!render && !usesMount) throw new NoRenderFunctionError({ id }); let defaultMount = (context) => async () => (await context.renderToCanvas(), context.canvas), mount = storyAnnotations.mount ?? componentAnnotations.mount ?? projectAnnotations.mount ?? defaultMount, testingLibraryRender = projectAnnotations.testingLibraryRender; return { storyGlobals: {}, ...partialAnnotations, moduleExport, id, name, story: name, originalStoryFn: render, undecoratedStoryFn, unboundStoryFn, applyLoaders, applyBeforeEach, applyAfterEach, playFunction, runStep, mount, testingLibraryRender, renderToCanvas: projectAnnotations.renderToCanvas, usesMount }; } function prepareMeta(componentAnnotations, projectAnnotations, moduleExport) { return { ...preparePartialAnnotations(void 0, componentAnnotations, projectAnnotations), moduleExport }; } function preparePartialAnnotations(storyAnnotations, componentAnnotations, projectAnnotations) { let defaultTags = [Tag.DEV, Tag.TEST], extraTags = globalThis2.DOCS_OPTIONS?.autodocs === !0 ? [Tag.AUTODOCS] : [], overrideTags = storyAnnotations?.tags?.includes(Tag.TEST_FN) ? [`!${Tag.AUTODOCS}`] : [], tags = combineTags( ...defaultTags, ...extraTags, ...projectAnnotations.tags ?? [], ...componentAnnotations.tags ?? [], ...overrideTags, ...storyAnnotations?.tags ?? [] ), parameters = combineParameters( projectAnnotations.parameters, componentAnnotations.parameters, storyAnnotations?.parameters ), { argTypesEnhancers = [], argsEnhancers = [] } = projectAnnotations, passedArgTypes = combineParameters( projectAnnotations.argTypes, componentAnnotations.argTypes, storyAnnotations?.argTypes ); if (storyAnnotations) { let render = storyAnnotations?.userStoryFn || storyAnnotations?.render || componentAnnotations.render || projectAnnotations.render; parameters.__isArgsStory = render && render.length > 0; } let passedArgs = { ...projectAnnotations.args, ...componentAnnotations.args, ...storyAnnotations?.args }, storyGlobals = { ...componentAnnotations.globals, ...storyAnnotations?.globals }, contextForEnhancers = { componentId: componentAnnotations.id, title: componentAnnotations.title, kind: componentAnnotations.title, // Back compat id: storyAnnotations?.id || componentAnnotations.id, // if there's no story name, we create a fake one since enhancers expect a name name: storyAnnotations?.name || "__meta", story: storyAnnotations?.name || "__meta", // Back compat component: componentAnnotations.component, subcomponents: componentAnnotations.subcomponents, tags, parameters, initialArgs: passedArgs, argTypes: passedArgTypes, storyGlobals }; contextForEnhancers.argTypes = argTypesEnhancers.reduce( (accumulatedArgTypes, enhancer) => enhancer({ ...contextForEnhancers, argTypes: accumulatedArgTypes }), contextForEnhancers.argTypes ); let initialArgsBeforeEnhancers = { ...passedArgs }; contextForEnhancers.initialArgs = [...argsEnhancers].reduce( (accumulatedArgs, enhancer) => ({ ...accumulatedArgs, ...enhancer({ ...contextForEnhancers, initialArgs: accumulatedArgs }) }), initialArgsBeforeEnhancers ); let { name, story, ...withoutStoryIdentifiers } = contextForEnhancers; return withoutStoryIdentifiers; } function prepareContext(context) { let { args: unmappedArgs } = context, targetedContext = { ...context, allArgs: void 0, argsByTarget: void 0 }; if (global3.FEATURES?.argTypeTargetsV7) { let argsByTarget = groupArgsByTarget(context); targetedContext = { ...context, allArgs: context.args, argsByTarget, args: argsByTarget[UNTARGETED] || {} }; } let mappedArgs = Object.entries(targetedContext.args).reduce((acc, [key, val]) => { if (!targetedContext.argTypes[key]?.mapping) return acc[key] = val, acc; let mappingFn = (originalValue) => { let mapping = targetedContext.argTypes[key].mapping; return mapping && originalValue in mapping ? mapping[originalValue] : originalValue; }; return acc[key] = Array.isArray(val) ? val.map(mappingFn) : mappingFn(val), acc; }, {}), includedArgs = Object.entries(mappedArgs).reduce((acc, [key, val]) => { let argType = targetedContext.argTypes[key] || {}; return includeConditionalArg(argType, mappedArgs, targetedContext.globals) && (acc[key] = val), acc; }, {}); return { ...targetedContext, unmappedArgs, args: includedArgs }; } // src/preview-api/modules/store/inferArgTypes.ts import { logger as logger5 } from "storybook/internal/client-logger"; var inferType = (value, name, visited, cache) => { let type = typeof value; switch (type) { case "boolean": case "string": case "number": case "function": case "symbol": return { name: type }; default: break; } if (value) { if (cache.has(value)) return cache.get(value); if (visited.has(value)) return logger5.warn(dedent` We've detected a cycle in arg '${name}'. Args should be JSON-serializable. Consider using the mapping feature or fully custom args: - Mapping: https://storybook.js.org/docs/writing-stories/args#mapping-to-complex-arg-values - Custom args: https://storybook.js.org/docs/essentials/controls#fully-custom-args `), { name: "other", value: "cyclic object" }; visited.add(value); let result; return Array.isArray(value) ? result = { name: "array", value: value.length > 0 ? inferType(value[0], name, visited, cache) : { name: "other", value: "unknown" } } : result = { name: "object", value: mapValues(value, (field) => inferType(field, name, visited, cache)) }, visited.delete(value), cache.set(value, result), result; } return { name: "object", value: {} }; }, inferArgTypes = (context) => { let { id, argTypes: userArgTypes = {}, initialArgs = {} } = context, cache = /* @__PURE__ */ new Map(), argTypes = mapValues(initialArgs, (arg, key) => ({ name: key, type: inferType(arg, `${id}.${key}`, /* @__PURE__ */ new Set(), cache) })), userArgTypesNames = mapValues(userArgTypes, (argType, key) => ({ name: key })); return combineParameters(argTypes, userArgTypesNames, userArgTypes); }; inferArgTypes.secondPass = !0; // src/preview-api/modules/store/inferControls.ts import { logger as logger6 } from "storybook/internal/client-logger"; // src/preview-api/modules/store/filterArgTypes.ts var matches = (name, descriptor) => Array.isArray(descriptor) ? descriptor.includes(name) : name.match(descriptor), filterArgTypes = (argTypes, include, exclude) => !include && !exclude ? argTypes : argTypes && pickBy(argTypes, (argType, key) => { let name = argType.name || key.toString(); return !!(!include || matches(name, include)) && (!exclude || !matches(name, exclude)); }); // src/preview-api/modules/store/inferControls.ts var inferControl = (argType, name, matchers) => { let { type, options } = argType; if (type) { if (matchers.color && matchers.color.test(name)) { let controlType = type.name; if (controlType === "string") return { control: { type: "color" } }; controlType !== "enum" && logger6.warn( `Addon controls: Control of type color only supports string, received "${controlType}" instead` ); } if (matchers.date && matchers.date.test(name)) return { control: { type: "date" } }; switch (type.name) { case "array": return { control: { type: "object" } }; case "boolean": return { control: { type: "boolean" } }; case "string": return { control: { type: "text" } }; case "number": return { control: { type: "number" } }; case "enum": { let { value } = type; return { control: { type: value?.length <= 5 ? "radio" : "select" }, options: value }; } case "function": case "symbol": return null; default: return { control: { type: options ? "select" : "object" } }; } } }, inferControls = (context) => { let { argTypes, parameters: { __isArgsStory, controls: { include = null, exclude = null, matchers = {} } = {} } } = context; if (!__isArgsStory) return argTypes; let filteredArgTypes = filterArgTypes(argTypes, include, exclude), withControls = mapValues(filteredArgTypes, (argType, name) => argType?.type && inferControl(argType, name.toString(), matchers)); return combineParameters(withControls, filteredArgTypes); }; inferControls.secondPass = !0; // src/preview-api/modules/store/csf/normalizeProjectAnnotations.ts function normalizeProjectAnnotations({ argTypes, argTypesEnhancers, decorators, loaders, beforeEach, afterEach, initialGlobals, ...annotations }) { return { ...argTypes && { argTypes: normalizeInputTypes(argTypes) }, decorators: normalizeArrays(decorators), loaders: normalizeArrays(loaders), beforeEach: normalizeArrays(beforeEach), afterEach: normalizeArrays(afterEach), argTypesEnhancers: [ ...argTypesEnhancers || [], inferArgTypes, // There's an architectural decision to be made regarding embedded addons in core: // // Option 1: Keep embedded addons but ensure consistency by moving addon-specific code // (like inferControls) to live alongside the addon code itself. This maintains the // concept of core addons while improving code organization. // // Option 2: Fully integrate these addons into core, potentially moving UI components // into the manager and treating them as core features rather than addons. This is a // bigger architectural change requiring careful consideration. // // For now, we're keeping inferControls here as we need time to properly evaluate // these options and their implications. Some features (like Angular's cleanArgsDecorator) // currently rely on this behavior. // // TODO: Make an architectural decision on the handling of core addons inferControls ], initialGlobals, ...annotations }; } // src/preview-api/modules/store/csf/composeConfigs.ts import { global as global4 } from "@storybook/global"; // src/preview-api/modules/store/csf/beforeAll.ts var composeBeforeAllHooks = (hooks) => async () => { let cleanups2 = []; for (let hook of hooks) { let cleanup = await hook(); cleanup && cleanups2.unshift(cleanup); } return async () => { for (let cleanup of cleanups2) await cleanup(); }; }; // src/preview-api/modules/store/csf/stepRunners.ts function composeStepRunners(stepRunners) { return async (label, play, playContext) => { await stepRunners.reduceRight( (innerPlay, stepRunner) => async () => stepRunner(label, innerPlay, playContext), async () => play(playContext) )(); }; } // src/preview-api/modules/store/csf/composeConfigs.ts function getField(moduleExportList, field) { return moduleExportList.map((xs) => xs.default?.[field] ?? xs[field]).filter(Boolean); } function getArrayField(moduleExportList, field, options = {}) { return getField(moduleExportList, field).reduce((prev, cur) => { let normalized = normalizeArrays(cur); return options.reverseFileOrder ? [...normalized, ...prev] : [...prev, ...normalized]; }, []); } function getObjectField(moduleExportList, field) { return Object.assign({}, ...getField(moduleExportList, field)); } function getSingletonField(moduleExportList, field) { return getField(moduleExportList, field).pop(); } function composeConfigs(moduleExportList) { let allArgTypeEnhancers = getArrayField(moduleExportList, "argTypesEnhancers"), stepRunners = getField(moduleExportList, "runStep"), beforeAllHooks = getArrayField(moduleExportList, "beforeAll"); return { parameters: combineParameters(...getField(moduleExportList, "parameters")), decorators: getArrayField(moduleExportList, "decorators", { reverseFileOrder: !(global4.FEATURES?.legacyDecoratorFileOrder ?? !1) }), args: getObjectField(moduleExportList, "args"), argsEnhancers: getArrayField(moduleExportList, "argsEnhancers"), argTypes: getObjectField(moduleExportList, "argTypes"), argTypesEnhancers: [ ...allArgTypeEnhancers.filter((e) => !e.secondPass), ...allArgTypeEnhancers.filter((e) => e.secondPass) ], initialGlobals: getObjectField(moduleExportList, "initialGlobals"), globalTypes: getObjectField(moduleExportList, "globalTypes"), loaders: getArrayField(moduleExportList, "loaders"), beforeAll: composeBeforeAllHooks(beforeAllHooks), beforeEach: getArrayField(moduleExportList, "beforeEach"), afterEach: getArrayField(moduleExportList, "afterEach"), render: getSingletonField(moduleExportList, "render"), renderToCanvas: getSingletonField(moduleExportList, "renderToCanvas"), applyDecorators: getSingletonField(moduleExportList, "applyDecorators"), runStep: composeStepRunners(stepRunners), tags: getArrayField(moduleExportList, "tags"), mount: getSingletonField(moduleExportList, "mount"), testingLibraryRender: getSingletonField(moduleExportList, "testingLibraryRender") }; } // src/preview-api/modules/store/csf/portable-stories.ts import { isExportStory as isExportStory2 } from "storybook/internal/csf"; import { getCoreAnnotations } from "storybook/internal/csf"; import { MountMustBeDestructuredError } from "storybook/internal/preview-errors"; // src/preview-api/modules/store/reporter-api.ts var ReporterAPI = class { constructor() { this.reports = []; } async addReport(report) { this.reports.push(report); } }; // src/preview-api/modules/store/csf/csf-factory-utils.ts import { isMeta, isStory as isStory2 } from "storybook/internal/csf"; function getCsfFactoryAnnotations(story, meta, projectAnnotations) { return isStory2(story) ? { story: story.input, meta: story.meta.input, preview: story.meta.preview.composed } : { story, meta: isMeta(meta) ? meta.input : meta, preview: projectAnnotations }; } // src/preview-api/modules/store/csf/portable-stories.ts function setDefaultProjectAnnotations(_defaultProjectAnnotations) { globalThis.defaultProjectAnnotations = _defaultProjectAnnotations; } var DEFAULT_STORY_TITLE = "ComposedStory", DEFAULT_STORY_NAME = "Unnamed Story"; function extractAnnotation(annotation) { return annotation ? composeConfigs([annotation]) : {}; } function setProjectAnnotations(projectAnnotations) { let annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations]; return globalThis.globalProjectAnnotations = composeConfigs([ ...getCoreAnnotations(), globalThis.defaultProjectAnnotations ?? {}, composeConfigs(annotations.map(extractAnnotation)) ]), globalThis.globalProjectAnnotations ?? {}; } var cleanups = []; function composeStory(storyAnnotations, componentAnnotations, projectAnnotations, defaultConfig, exportsName) { if (storyAnnotations === void 0) throw new Error("Expected a story but received undefined."); componentAnnotations.title = componentAnnotations.title ?? DEFAULT_STORY_TITLE; let normalizedComponentAnnotations = normalizeComponentAnnotations(componentAnnotations), storyName = exportsName || storyAnnotations.storyName || storyAnnotations.story?.name || storyAnnotations.name || DEFAULT_STORY_NAME, normalizedStory = normalizeStory( storyName, storyAnnotations, normalizedComponentAnnotations ), normalizedProjectAnnotations = normalizeProjectAnnotations( composeConfigs([ defaultConfig ?? globalThis.globalProjectAnnotations ?? {}, projectAnnotations ?? {} ]) ), story = prepareStory( normalizedStory, normalizedComponentAnnotations, normalizedProjectAnnotations ), globals = { ...getValuesFromGlobalTypes(normalizedProjectAnnotations.globalTypes), ...normalizedProjectAnnotations.initialGlobals, ...story.storyGlobals }, reporting = new ReporterAPI(), initializeContext = () => { let context = prepareContext({ hooks: new HooksContext(), globals, args: { ...story.initialArgs }, viewMode: "story", reporting, loaded: {}, abortSignal: new AbortController().signal, step: (label, play2) => story.runStep(label, play2, context), canvasElement: null, canvas: {}, userEvent: {}, globalTypes: normalizedProjectAnnotations.globalTypes, ...story, context: null, mount: null });